/*
 * Copyright (c) 2010-2011, Yawning <yawninglol at gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 *   * Neither the name of the Mew Developers nor the
 *     names of its contributors may be used to endorse or promote products
 *     derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */

package yawning.mew.pivot.feraldruid;

import java.io.IOException;
import java.text.DecimalFormat;
import java.util.Arrays;
import java.util.Properties;

import org.apache.pivot.serialization.SerializationException;
import org.apache.pivot.beans.BXML;
import org.apache.pivot.beans.BXMLSerializer;
import org.apache.pivot.collections.HashMap;
import org.apache.pivot.collections.LinkedList;
import org.apache.pivot.collections.List;
import org.apache.pivot.collections.Map;
import org.apache.pivot.json.JSON;
import org.apache.pivot.wtk.Button;
import org.apache.pivot.wtk.Checkbox;
import org.apache.pivot.wtk.Component;
import org.apache.pivot.wtk.ListButton;
import org.apache.pivot.wtk.Spinner;
import org.apache.pivot.wtk.SpinnerSelectionListener;
import org.apache.pivot.wtk.TableView;

import org.apache.pivot.wtk.TextInput;
import org.apache.pivot.wtk.content.NumericSpinnerData;

import yawning.mew.character.Profession;
import yawning.mew.character.Toon;
import yawning.mew.pivot.MewDoubleValidator;
import yawning.mew.pivot.MewIntValidator;
import yawning.mew.pivot.MewPivotGearBase;
import yawning.mew.pivot.MewNumericInputListener;
import yawning.mew.pivot.MewParse;
import yawning.mew.pivot.MewTableViewMouseButtonListener;
import yawning.mew.pivot.feraldruid.Loadables.LevelLoadables;
import yawning.mew.pivot.feraldruid.Loadables.ProcProfProp;

/**
 * Mew Pivot Feral Druid Gear specific UI components.
 *
 * TODO: This should have a gear editor.
 * TODO: The Proc UI will get cluttered eventually, add sorting and what not.
 *
 * @author Yawning <yawninglol at gmail.com>
 */
public class Gear extends MewPivotGearBase {
	@BXML private Component mComponent;

	@BXML private ListButton statRace;
	@BXML private Spinner statLevel;

	@BXML private TextInput statWeaponDPS;
	@BXML private ListButton statMetaGem;

	@BXML private TextInput statStamina;
	@BXML private TextInput statArmor;
	@BXML private TextInput statBonusArmor;

	@BXML private TextInput statStrength;
	@BXML private TextInput statAgility;
	@BXML private TextInput statAttackPower;

	@BXML private TextInput statHasteRating;
	@BXML private TextInput statCritRating;
	@BXML private TextInput statMasteryRating;

	@BXML private TextInput statHitRating;
	@BXML private TextInput statExpertiseRating;
	@BXML private TextInput statDodgeRating;

	@BXML private TableView statProps;

	@BXML private TableView statProcs;

	@BXML private ListButton statFood;
	@BXML private ListButton statFlask;
	@BXML private ListButton statPotion;
	@BXML private Checkbox statMixology;
	@BXML private Checkbox statPrePotion;

	@BXML private TableView statSetBonuses;

	private NumericSpinnerData statLevelData;

	private Loadables mLoadables;

	private HashMap<String,Object> foodMap;
	private HashMap<String,Object> flaskMap;
	private HashMap<String,Object> potionMap;

	public Gear() {
		BXMLSerializer bxmlSerializer = new BXMLSerializer();

		try {
			mComponent = (Component) bxmlSerializer.readObject(getClass().getResource("Gear.bxml"));
		} catch (IOException e) {
			e.printStackTrace();
		} catch (SerializationException e) {
			e.printStackTrace();
		}

		bxmlSerializer.bind(this);

		statRace.setSelectedIndex(0);

		mLoadables = Loadables.getInstance();

		// TODO: I should move all of these to WTKX.
		statWeaponDPS.setValidator(new MewDoubleValidator());
		statWeaponDPS.setStrictValidation(true);

		statStamina.setValidator(new MewIntValidator());
		statStamina.setStrictValidation(true);
		statArmor.setValidator(new MewIntValidator());
		statArmor.setStrictValidation(true);
		statBonusArmor.setValidator(new MewIntValidator());
		statBonusArmor.setStrictValidation(true);

		statStrength.setValidator(new MewIntValidator());
		statStrength.setStrictValidation(true);
		statAgility.setValidator(new MewIntValidator());
		statAgility.setStrictValidation(true);
		statAttackPower.setValidator(new MewIntValidator());
		statAttackPower.setStrictValidation(true);

		statHasteRating.setValidator(new MewIntValidator());
		statHasteRating.setStrictValidation(true);
		statCritRating.setValidator(new MewIntValidator());
		statCritRating.setStrictValidation(true);
		statMasteryRating.setValidator(new MewIntValidator());
		statMasteryRating.setStrictValidation(true);

		statHitRating.setValidator(new MewIntValidator());
		statHitRating.setStrictValidation(true);
		statExpertiseRating.setValidator(new MewIntValidator());
		statExpertiseRating.setStrictValidation(true);
		statDodgeRating.setValidator(new MewIntValidator());
		statDodgeRating.setStrictValidation(true);

		statLevelData = (NumericSpinnerData) statLevel.getSpinnerData();
		statLevel.getSpinnerSelectionListeners().add(new SpinnerSelectionListener.Adapter() {
			@Override
			public void selectedIndexChanged(Spinner spinner, int previousSelectedIndex) {
				onLevelChange();
			}
		});

		statProps.getComponentMouseButtonListeners().add(new MewTableViewMouseButtonListener());
		statProcs.getComponentMouseButtonListeners().add(new MewTableViewMouseButtonListener());
		statSetBonuses.getComponentMouseButtonListeners().add(new MewTableViewMouseButtonListener());

		// Add listeners to fix up zero length text for inputs where we expect numbers.
		statWeaponDPS.getComponentStateListeners().add(new MewNumericInputListener());
		statStamina.getComponentStateListeners().add(new MewNumericInputListener());
		statArmor.getComponentStateListeners().add(new MewNumericInputListener());
		statBonusArmor.getComponentStateListeners().add(new MewNumericInputListener());
		statStrength.getComponentStateListeners().add(new MewNumericInputListener());
		statAgility.getComponentStateListeners().add(new MewNumericInputListener());
		statAttackPower.getComponentStateListeners().add(new MewNumericInputListener());
		statHasteRating.getComponentStateListeners().add(new MewNumericInputListener());
		statCritRating.getComponentStateListeners().add(new MewNumericInputListener());
		statMasteryRating.getComponentStateListeners().add(new MewNumericInputListener());
		statHitRating.getComponentStateListeners().add(new MewNumericInputListener());
		statExpertiseRating.getComponentStateListeners().add(new MewNumericInputListener());
		statDodgeRating.getComponentStateListeners().add(new MewNumericInputListener());

		// Set this to 85 by default.
		statLevel.setSelectedIndex(statLevelData.indexOf(85));
		onLevelChange();
	}

	public Component getComponent() {
		return mComponent;
	}

	private void onLevelChange() {
		loadProcs();
		loadConsumables();
	}

	@SuppressWarnings("unchecked")
	public Properties toProperties() {
		Properties rval = new Properties();

		String race = null;
		if (statRace.getSelectedIndex() != -1) {
			race = (String) statRace.getListData().get(statRace.getSelectedIndex());
		}

		String dpsStr = statWeaponDPS.getText().replace(',', '.');
		double dps = MewParse.parseDouble(dpsStr);

		int level = statLevelData.get(statLevel.getSelectedIndex());
		rval.setProperty("Level", Integer.toString(level));

		// Scale this by 100 so we can get 2 significant figures without rounding errors.
		rval.setProperty("WeaponTop", Integer.toString((int) Math.round(dps * 100 + 1)));
		rval.setProperty("WeaponBottom", Integer.toString((int) Math.round(dps * 100 - 1)));
		rval.setProperty("WeaponSpeed", "100000");

		int stam = MewParse.parseInt(statStamina.getText());
		int armor = MewParse.parseInt(statArmor.getText());
		int bonusArmor = MewParse.parseInt(statBonusArmor.getText());
		int str = MewParse.parseInt(statStrength.getText());
		int agi = MewParse.parseInt(statAgility.getText());
		int ap = MewParse.parseInt(statAttackPower.getText());
		int haste = MewParse.parseInt(statHasteRating.getText());
		int crit = MewParse.parseInt(statCritRating.getText());
		int mastery = MewParse.parseInt(statMasteryRating.getText());
		int hit = MewParse.parseInt(statHitRating.getText());
		int expertise = MewParse.parseInt(statExpertiseRating.getText());
		int dodge = MewParse.parseInt(statDodgeRating.getText());

		String metaGem = null;
		if (statMetaGem.getSelectedIndex() != -1) {
			metaGem = (String) statMetaGem.getListData().get(statMetaGem.getSelectedIndex());
		}

		// Tooltip/Armory Attack Power includes Strength and Aggression, subtract that now.
		ap = (int) Math.floor((double) ap / 125 * 100); // Aggression: 1.25x AP
		ap = ap - (str - 10) * 1; // Str -> 1 AP per, past the first 10.

		// Tooltip/Armory Armor includes bonus armor / metagem, subtract that now.
		if (armor > 0) {
			double armorMult = 1.0;
			armorMult *= (metaGem != null && metaGem.equals("Austere")) ? 1.02 : 1;
			armor -= bonusArmor;
			armor /= armorMult;
		}

		// Apply the Food/Flask if selected.
		LinkedList<Map<String,Object>> consumables = new LinkedList<Map<String,Object>>();
		boolean mixology = statMixology.getState().equals(Button.State.SELECTED);
		rval.setProperty("Simulator.PrePotion", Boolean.toString(statPrePotion.getState().equals(Button.State.SELECTED)));
		if (statFood.getSelectedIndex() != 0) {
			Map<String,Object> item = (Map<String, Object>) foodMap.get((String) statFood.getSelectedItem());
			consumables.add(item);
		}
		if (statFlask.getSelectedIndex() != 0) {
			Map<String,Object> item = (Map<String, Object>) flaskMap.get((String) statFlask.getSelectedItem());
			consumables.add(item);
		}
		for (Map<String,Object> item : consumables) {
			String strId = JSON.get(item, "prop");
			boolean isElixir = (strId.charAt(0) == 'e');
			String sufix = (isElixir && mixology) ? "Mix" : "";

			if (item.containsKey("buffStamina" + sufix)) stam += JSON.getInt(item, "buffStamina" + sufix);
			if (item.containsKey("buffAgility" + sufix)) agi += JSON.getInt(item, "buffAgility" + sufix);
			if (item.containsKey("buffStrength" + sufix)) str += JSON.getInt(item, "buffStrength" + sufix);
			if (item.containsKey("buffAttackPower" + sufix)) ap += JSON.getInt(item, "buffAttackPower" + sufix);
			if (item.containsKey("buffHasteRating" + sufix)) haste += JSON.getInt(item, "buffHasteRating" + sufix);
			if (item.containsKey("buffCritRating" + sufix)) crit += JSON.getInt(item, "buffCritRating" + sufix);
			if (item.containsKey("buffMasteryRating" + sufix)) mastery += JSON.getInt(item, "buffMasteryRating" + sufix);
			if (item.containsKey("buffHitRating" + sufix)) hit += JSON.getInt(item, "buffHitRating" + sufix);
			if (item.containsKey("buffExpertiseRating" + sufix)) expertise += JSON.getInt(item, "buffExpertiseRating" + sufix);
			if (item.containsKey("buffDodgeRating" + sufix)) dodge += JSON.getInt(item, "buffDodgeRating" + sufix);
		}

		rval.setProperty("Race", race);
		rval.setProperty("Stamina", Integer.toString(stam));
		rval.setProperty("Armor", Integer.toString(armor));
		rval.setProperty("BonusArmor", Integer.toString(bonusArmor));
		rval.setProperty("Agility", Integer.toString(agi));
		rval.setProperty("Strength", Integer.toString(str));
		rval.setProperty("AttackPower", Integer.toString(ap));
		rval.setProperty("HasteRating", Integer.toString(haste));
		rval.setProperty("CritRating", Integer.toString(crit));
		rval.setProperty("MasteryRating", Integer.toString(mastery));
		rval.setProperty("HitRating", Integer.toString(hit));
		rval.setProperty("ExpertiseRating", Integer.toString(expertise));
		rval.setProperty("DodgeRating", Integer.toString(dodge));

		if (metaGem != null) rval.setProperty("MetaGem", metaGem);

		for (Map<String,Object> row : (List<Map<String,Object>>) statProps.getTableData()) {
			String key = JSON.get(row, "prop");
			boolean value = (Boolean) JSON.get(row, "enabled");

			rval.setProperty(key, Boolean.toString(value));
		}

		for (Map<String,Object> row : (List<Map<String,Object>>) statSetBonuses.getTableData()) {
			String key = JSON.get(row, "prop");
			boolean value = (Boolean) JSON.get(row, "enabled");

			rval.setProperty(key, Boolean.toString(value));
		}

		String spellProcs = "";
		String itemProcs = "";
		int nrKirilPolearms = 0;
		for (Map<String,Object> row : (List<Map<String,Object>>) statProcs.getTableData()) {
			String key = JSON.get(row, "prop");
			boolean value = (Boolean) JSON.get(row, "enabled");

			if (value == true) {
				int id = Integer.parseInt(key.substring(1));
				if (key.charAt(0) == 'i') {
					itemProcs += "," + id;

					if (KIRIL_ITEMS.contains(id)) nrKirilPolearms++;

					// Ricket's Magnetic Fireball has a secondary effect tied to it.
					if (id == 70144) {
						spellProcs += "," + 95871;
					}
				} else {
					spellProcs += "," + id;
				}
			}
		}
		if (race.equals("Troll")) { spellProcs += ",26297"; }
		if (spellProcs.length() > 0) { rval.setProperty("Procs.spells", spellProcs.substring(1)); }
		if (itemProcs.length() > 0) { rval.setProperty("Procs.items", itemProcs.substring(1)); }

		if (statPotion.getSelectedIndex() != 0) {
			Map<String,Object> item = (Map<String, Object>) potionMap.get((String) statPotion.getSelectedItem());
			String strId = JSON.get(item, "prop");

			rval.setProperty("Procs.potion", strId.substring(1));
		}

		// HACK - Prevent multiple instances of Kiril, Fury of the Beasts from being used at once.
		rval.setProperty("Pivot.nrKirilPolearms", Integer.toString(nrKirilPolearms));

		return rval;
	}

	@SuppressWarnings("unchecked")
	public void onLoad(Map<String,Object> backingStore) {
		Map<String,?> tmp;

		tmp = JSON.get(backingStore, "stat");

		if (tmp.containsKey("race")) { statRace.setSelectedItem((String) tmp.get("race")); }
		if (tmp.containsKey("level")) {
			int idx = statLevelData.indexOf((Integer) tmp.get("level"));
			statLevel.setSelectedIndex(idx);
			onLevelChange();
		}
		if (tmp.containsKey("weaponDPS")) { statWeaponDPS.setText(tmp.get("weaponDPS").toString()); }
		if (tmp.containsKey("stamina")) { statStamina.setText(tmp.get("stamina").toString()); }
		if (tmp.containsKey("armor")) { statArmor.setText(tmp.get("armor").toString()); }
		if (tmp.containsKey("bonusArmor")) { statBonusArmor.setText(tmp.get("bonusArmor").toString()); }
		if (tmp.containsKey("strength")) { statStrength.setText(tmp.get("strength").toString()); }
		if (tmp.containsKey("agility")) { statAgility.setText(tmp.get("agility").toString()); }
		if (tmp.containsKey("attackPower")) { statAttackPower.setText(tmp.get("attackPower").toString()); }
		if (tmp.containsKey("hasteRating")) { statHasteRating.setText(tmp.get("hasteRating").toString()); }
		if (tmp.containsKey("critRating")) { statCritRating.setText(tmp.get("critRating").toString()); }
		if (tmp.containsKey("masteryRating")) { statMasteryRating.setText(tmp.get("masteryRating").toString()); }
		if (tmp.containsKey("hitRating")) { statHitRating.setText(tmp.get("hitRating").toString()); }
		if (tmp.containsKey("expertiseRating")) { statExpertiseRating.setText(tmp.get("expertiseRating").toString()); }
		if (tmp.containsKey("dodgeRating")) { statDodgeRating.setText(tmp.get("dodgeRating").toString()); }

		if (tmp.containsKey("metaGem")) { statMetaGem.setSelectedItem((String) tmp.get("metaGem")); }

		// Backwards compatibility, meta gem used to be a property.
		tmp = JSON.get(backingStore, "statProps");
		if (tmp != null) {
			if (tmp.containsKey("RelentlessMeta") && (Boolean) tmp.get("RelentlessMeta")) {
				statMetaGem.setSelectedItem("Agile");	// Treat the old RelentlessMeta property as Agile.
			}
		}

		tmp = JSON.get(backingStore, "statProps");
		if (tmp != null) {
			for (Map<String,Object> row : (List<Map<String,Object>>) statProps.getTableData()) {
				String key = JSON.get(row, "prop");

				if (tmp.containsKey(key)) {
					boolean enabled = (Boolean) tmp.get(key);
					JSON.put(row, "enabled", enabled);
				}
			}
		}

		tmp = JSON.get(backingStore, "statSetBonuses");
		if (tmp != null) {
			for (Map<String,Object> row : (List<Map<String,Object>>) statSetBonuses.getTableData()) {
				String key = JSON.get(row, "prop");

				if (tmp.containsKey(key)) {
					boolean enabled = (Boolean) tmp.get(key);
					JSON.put(row, "enabled", enabled);
				}
			}
		}

		tmp = JSON.get(backingStore, "statProcs");
		if (tmp != null) {
			for (Map<String,Object> row : (List<Map<String,Object>>) statProcs.getTableData()) {
				String key = JSON.get(row, "prop");

				if (tmp.containsKey(key)) {
					boolean enabled = (Boolean) tmp.get(key);
					JSON.put(row, "enabled", enabled);
				}
			}
		}

		tmp = JSON.get(backingStore, "statConsumables");
		if (tmp != null) {
			if (tmp.containsKey("Food")) {
				String name = (String) tmp.get("Food");
				if (foodMap.containsKey(name)) statFood.setSelectedItem(name);
			}
			if (tmp.containsKey("Flask")) {
				String name = (String) tmp.get("Flask");
				if (flaskMap.containsKey(name)) statFlask.setSelectedItem(name);
			}
			if (tmp.containsKey("Potion")) {
				String name = (String) tmp.get("Potion");
				if (potionMap.containsKey(name)) statPotion.setSelectedItem(name);
			}
			if (tmp.containsKey("Mixology")) { statMixology.setState((tmp.get("Mixology").equals(Boolean.TRUE)) ? Button.State.SELECTED : Button.State.UNSELECTED); }
			if (tmp.containsKey("PrePotion")) { statPrePotion.setState((tmp.get("PrePotion").equals(Boolean.TRUE)) ? Button.State.SELECTED: Button.State.UNSELECTED); }
		}
	}

	@SuppressWarnings("unchecked")
	public void onSave(Map<String,Object> backingStore) {
		HashMap<String,Object> tmp;

		tmp = new HashMap<String,Object>();

		if (statRace.getSelectedIndex() != -1) {
			tmp.put("race", (String) statRace.getListData().get(statRace.getSelectedIndex()));
		}

		tmp.put("level", statLevelData.get(statLevel.getSelectedIndex()));
		tmp.put("weaponDPS", MewParse.parseDouble(statWeaponDPS.getText()));
		tmp.put("stamina", MewParse.parseInt(statStamina.getText()));
		tmp.put("armor", MewParse.parseInt(statArmor.getText()));
		tmp.put("bonusArmor", MewParse.parseInt(statBonusArmor.getText()));
		tmp.put("strength", MewParse.parseInt(statStrength.getText()));
		tmp.put("agility", MewParse.parseInt(statAgility.getText()));
		tmp.put("attackPower", MewParse.parseInt(statAttackPower.getText()));
		tmp.put("hasteRating", MewParse.parseInt(statHasteRating.getText()));
		tmp.put("critRating", MewParse.parseInt(statCritRating.getText()));
		tmp.put("masteryRating", MewParse.parseInt(statMasteryRating.getText()));
		tmp.put("hitRating", MewParse.parseInt(statHitRating.getText()));
		tmp.put("expertiseRating", MewParse.parseInt(statExpertiseRating.getText()));
		tmp.put("dodgeRating", MewParse.parseInt(statDodgeRating.getText()));

		if (statMetaGem.getSelectedIndex() != -1) {
			tmp.put("metaGem", (String) statMetaGem.getListData().get(statMetaGem.getSelectedIndex()));
		}

		JSON.put(backingStore, "stat", tmp);

		tmp = new HashMap<String,Object>();
		for (Map<String,Object> row : (List<Map<String,Object>>) statProps.getTableData()) {
			String key = JSON.get(row, "prop");
			boolean value = (Boolean) JSON.get(row, "enabled");

			tmp.put(key, value);
		}
		JSON.put(backingStore, "statProps", tmp);

		tmp = new HashMap<String,Object>();
		for (Map<String,Object> row : (List<Map<String,Object>>) statSetBonuses.getTableData()) {
			String key = JSON.get(row, "prop");
			boolean value = (Boolean) JSON.get(row, "enabled");

			tmp.put(key, value);
		}
		JSON.put(backingStore, "statSetBonuses", tmp);

		tmp = new HashMap<String,Object>();
		for (Map<String,Object> row : (List<Map<String,Object>>) statProcs.getTableData()) {
			String key = JSON.get(row, "prop");
			boolean value = (Boolean) JSON.get(row, "enabled");

			tmp.put(key, value);
		}
		JSON.put(backingStore, "statProcs", tmp);

		tmp = new HashMap<String,Object>();
		tmp.put("Mixology", statMixology.getState().equals(Button.State.SELECTED));
		tmp.put("Food", statFood.getSelectedItem());
		tmp.put("Flask", statFlask.getSelectedItem());
		tmp.put("Potion", statPotion.getSelectedItem());
		tmp.put("PrePotion", statPrePotion.getState().equals(Button.State.SELECTED));
		JSON.put(backingStore, "statConsumables", tmp);
	}

	private static final java.util.List<Integer> TIER_11_SET_ITEMS = Arrays.asList(
		60286,	// Stormrider's Headpeice (359)
		60288,	// Stormrider's Legguards (359)
		60287,	// Stormrider's Raiment (359)
		60289,	// Stormrider's Spaulders (359)
		60290,	// Stormrider's Grips (359)

		65189,	// Stormrider's Grips (372)
		65190,	// Stormrider's Headpeice (372)
		65191,	// Stormrider's Legguards (372)
		65192,	// Stormrider's Raiment (372)
		65193	// Stormrider's Spaulders (372)
	);
	private static final java.util.List<Integer> TIER_12_SET_ITEMS = Arrays.asList(
		71097,	// Obsidian Arborewave Grips (378)
		71098,	// Obsidian Arborweave Headpeice (378)
		71099,	// Obsidian Arborweave Leggards (378)
		71100,	// Obsidian Arborweave Raiment (378)
		71101,	// Obsidian Arborweave Spaulders (378)

		71486,	// Obsidian Arborweave Raiment (391)
		71487,	// Obsidian Arborewave Grips (391)
		71488,	// Obsidian Arborweave Headpeice (391)
		71489,	// Obsidian Arborweave Leggards (391)
		71490	// Obsidian Arborweave Spaulders (391)
	);
	private static final java.util.List<Integer> TIER_13_SET_ITEMS = Arrays.asList(
		78760,	// Deep Earth Raiment (384)
		78779,	// Deep Earth Grips (384)
		78789,	// Deep Earth Headpeice (384)
		78808,	// Deep Earth Legguards (384)
		78838,	// Deep Earth Spaulders (384)

		77013,	// Deep Earth Raiment (397)
		77014,	// Deep Earth Grips (397)
		77015,	// Deep Earth Headpeice (397)
		77016,	// Deep Earth Legguards (397)
		77017,	// Deep Earth Spaulders (397)

		78665,	// Deep Earth Raiment (410)
		78684,	// Deep Earth Grips (410)
		78694,	// Deep Earth Headpeice (410)
		78713,	// Deep Earth Legguards (410)
		78743	// Deep Earth Spaulders (410)
	);
	private static final java.util.List<Integer> PVP_GLOVES_ITEMS = Arrays.asList(
		64727,	// Bloodthirsty Gladiator's Dragonhide Gloves
		60443,	// Vicious Gladiator's Dragonhide Gloves
		65589,	// Vicious Gladiator's Dragonhide Gloves (2200)
		70279,	// Ruthless Gladiator's Dragonhide Gloves
		70486,	// Ruthless Gladiator's Dragonhide Gloves (2200)
		73615,	// Cataclysmic Gladiator's Dragonhide Gloves
		73737	// Cataclysmic Gladiator's Dragonhide Gloves (2200?)
	);
	private static final java.util.List<Integer> KIRIL_ITEMS = Arrays.asList(
		78473,	// Kiril, Fury of the Beasts (416)
		77194,	// Kiril, Fury of the Beasts (403)
		78482	// Kiril, Fury of the Beasts (390)
	);

	@SuppressWarnings("unchecked")
	@Override
	public void onImport(Toon toon, List<Integer> items, List<Integer> enchants, List<Integer> tinkers) {
		statRace.setSelectedItem(toon.mRace.toString());
		statLevel.setSelectedIndex(statLevelData.indexOf(toon.level));
		statStamina.setText(Integer.toString(toon.stamina));
		statArmor.setText(Integer.toString(toon.armor));
		statBonusArmor.setText(Integer.toString(toon.bonusArmor));
		statStrength.setText(Integer.toString(toon.strength));
		statAgility.setText(Integer.toString(toon.agility));
		statAttackPower.setText(Integer.toString(toon.attackPower));
		statHasteRating.setText(Integer.toString(toon.hasteRating));
		statCritRating.setText(Integer.toString(toon.critRating));
		statMasteryRating.setText(Integer.toString(toon.masteryRating));
		statHitRating.setText(Integer.toString(toon.hitRating));
		statExpertiseRating.setText(Integer.toString(toon.expertiseRating));
		statDodgeRating.setText(Integer.toString(toon.mDodgeRating));

		double weaponDmgSwing = (double) (toon.weaponTop + toon.weaponBottom) / 2.0;
		double weaponSpeed = (double) toon.weaponSpeed / 1000;
		statWeaponDPS.setText(new DecimalFormat("####.##").format(weaponDmgSwing / weaponSpeed));

		if (toon.mMetaGem != null) {
			switch(toon.mMetaGem) {
			case AUSTERE:
				statMetaGem.setSelectedItem("Austere");
				break;
			case AGILE:
				statMetaGem.setSelectedItem("Agile");
				break;
			case EFFULGENT:
				statMetaGem.setSelectedItem("Effulgent");
				break;
			default:
				// Not supported, sucks to be you.
			}
		}
		onLevelChange();

		// Handle properties.
		for (Map<String,Object> row : (List<Map<String,Object>>) statProps.getTableData()) {
			int idx = ((List<Map<String,Object>>)statProps.getTableData()).indexOf(row);
			String key = JSON.get(row, "prop");

			if (key.equals("ArmorSpec")) JSON.put(row, "enabled", toon.isArmorSpec);
			((List<Map<String,Object>>)statProps.getTableData()).update(idx, row);
		}

		// Handle set bonuses
		int nrTier11 = 0, nrTier12 = 0, nrTier13 = 0;
		boolean hasPvPGloves = false;
		for (int itemId : items) {
			if (TIER_11_SET_ITEMS.contains(itemId)) nrTier11++;
			if (TIER_12_SET_ITEMS.contains(itemId)) nrTier12++;
			if (TIER_13_SET_ITEMS.contains(itemId)) nrTier13++;
			if (PVP_GLOVES_ITEMS.contains(itemId)) hasPvPGloves = true;
		}
		for (Map<String,Object> row : (List<Map<String,Object>>) statSetBonuses.getTableData()) {
			int idx = ((List<Map<String,Object>>)statSetBonuses.getTableData()).indexOf(row);

			JSON.put(row, "enabled", false);

			if (row.containsKey("reqPvpGloves")) {
				if (hasPvPGloves == true) JSON.put(row, "enabled", true);
			}
			if (row.containsKey("reqTier11")) {
				if (nrTier11 >= JSON.getInt(row, "reqTier11")) JSON.put(row, "enabled", true);
			}
			if (row.containsKey("reqTier12")) {
				if (nrTier12 >= JSON.getInt(row, "reqTier12")) JSON.put(row, "enabled", true);
			}
			if (row.containsKey("reqTier13")) {
				if (nrTier13 >= JSON.getInt(row, "reqTier13")) JSON.put(row, "enabled", true);
			}

			((List<Map<String,Object>>)statSetBonuses.getTableData()).update(idx, row);
		}

		// Procs, Enchants, Tinkers
		List<Map<String,Object>> statProcsList = (List<Map<String,Object>>) statProcs.getTableData();
		HashMap<String, Map<String,Object>> statProcsMap = new HashMap<String, Map<String,Object>>();

		for (Map<String,Object> row : statProcsList) {
			statProcsMap.put((String) JSON.get(row, "prop"), row);
			JSON.put(row, "enabled", false);
		}

		// Handle trinket procs
		for (int itemId : items) {
			if (itemId == 62463) itemId = 62468;	// Unsolvable Riddle has a horde version.
			if (statProcsMap.containsKey("i" + itemId)) {
				Map<String,Object> row = statProcsMap.get("i" + itemId);
				JSON.put(row, "enabled", true);
			}
		}

		LevelLoadables levelData = mLoadables.get(statLevelData.get(statLevel.getSelectedIndex()));

		// Handle enchants
		for (int enchantId : enchants) {
			if (levelData.mEnchantMap.containsKey(enchantId)) {
				String strId = levelData.mEnchantMap.get(enchantId);
				ProcProfProp prop = levelData.mProfMap.get(strId);
				if (prop != null && !toon.hasProfession(prop.mProf, prop.mMinSkill)) continue;

				Map<String,Object> row = statProcsMap.get(strId);
				JSON.put(row, "enabled", true);
			}
		}

		// Handle tinkers
		for (int enchantId : tinkers) {
			if (levelData.mEnchantMap.containsKey(enchantId)) {
				String strId = levelData.mEnchantMap.get(enchantId);
				ProcProfProp prop = levelData.mProfMap.get(strId);
				if (prop != null && !toon.hasProfession(prop.mProf, prop.mMinSkill)) continue;

				Map<String,Object> row = statProcsMap.get(strId);
				JSON.put(row, "enabled", true);
			}
		}

		// Handle profession perks (lollifeblood)
		for (String strId : levelData.mProfMap) {
			ProcProfProp prop = levelData.mProfMap.get(strId);
			if (prop.mIsPerk && toon.hasProfession(prop.mProf, prop.mMinSkill)) {
				Map<String,Object> row = statProcsMap.get(strId);
				JSON.put(row, "enabled", true);
			}
		}

		List<Map<String,Object>> data = (List<Map<String,Object>>) statProcs.getTableData();
		for (int i = 0; i < statProps.getTableData().getLength(); i++) {
			Map<String,Object> row = (Map<String, Object>) data.get(i);
			data.update(i, row);
		}

		// Mixology (The HP flask only requires 500 skill, but the HP flask sucks.)
		statMixology.setState(toon.hasProfession(Profession.ALCHEMY, 510) ? Button.State.SELECTED : Button.State.UNSELECTED);
	}

	private void loadProcs() {
		LevelLoadables data = mLoadables.get(statLevelData.get(statLevel.getSelectedIndex()));

		LinkedList<Map<String,Object>> procList = new LinkedList<Map<String,Object>>();
		for (Map<String,Object> row : data.mProcList) {
			if (row.containsKey("hidden") && ((Boolean) row.get("hidden")) == true) {
				continue;
			}
			procList.add(new HashMap<String,Object>(row));
		}

		statProcs.setTableData(procList);
	}

	@SuppressWarnings("unchecked")
	private void loadConsumables()
	{
		LevelLoadables data = mLoadables.get(statLevelData.get(statLevel.getSelectedIndex()));

		statFood.getListData().clear();
		statFlask.getListData().clear();
		statPotion.getListData().clear();

		((List<Object>) statFood.getListData()).add("None");
		for (String name: data.mFoodList) {
			((List<Object>) statFood.getListData()).add(name);
		}
		foodMap = data.mFoodMap;

		((List<Object>) statFlask.getListData()).add("None");
		for (String name: data.mFlaskList) {
			((List<Object>) statFlask.getListData()).add(name);
		}
		flaskMap = data.mFlaskMap;

		((List<Object>) statPotion.getListData()).add("None");
		for (String name: data.mPotionList) {
			((List<Object>) statPotion.getListData()).add(name);
		}
		potionMap = data.mPotionMap;

		statFood.setSelectedIndex(0);
		statFlask.setSelectedIndex(0);
		statPotion.setSelectedIndex(0);
	}
}
